function [map_out, flag_out] = rgbedit(map_fig, control_command, h_line, vertex_index);

% RGBEDIT - interactive colormap editor
%
%    [NEW, FLAG] = RGBEDIT(OLD) allows the user to interactively
%    edit the colormap OLD.  The updated colormap is returned as
%    the array NEW, and FLAG indicates how the editor window was
%    exited (0 if Cancel, 1 if OK).
%
%    RGBEDIT(H_FIG, 'line', H_LINE) determines what action is
%    to be taken when a mouse click on a profile line H_LINE
%    is detected.
%
%    RGBEDIT(H_FIG, 'select', H_LINE) changes the selected RGB
%    profile to the color specified by H_LINE. 
%
%    RGBEDIT(H_FIG, 'move', H_LINE, INDEX) allows the user to
%    move the point INDEX on H_LINE. 
%
%    RGBEDIT(H_FIG, 'motion', H_LINE, INDEX) serves as the
%    WindowButtonMotionFcn as the user moves the selected point. 
%
%    RGBEDIT(H_FIG, 'add', H_LINE, X_PT) adds a vertex to the
%    selected RGB profile at the location specified by X_PT,
%    which contains the X coordinate of the mouse click.
%
%    RGBEDIT(H_FIG, 'delete', H_LINE, INDEX) deletes a vertex
%    from the selected RGB profile at the location specified
%    by INDEX. 
%
%    RGBEDIT(H_FIG, 'update') updates the colorbar display.
%
%    [NEW, CBAR] = RGBEDIT(H_FIG, 'calculate') returns the
%    updated color map and colorbar.

% By:   S.C. Molitor (smolitor@med.unc.edu)
% Date: May 19, 2000

% default parameters

color_list = {'red', 'green', 'blue'};		% color list
tol_scale = 0.01;		% 2nd derivative tolerance for linear profile

% global variables

global MAP_SIZE		% size of original colormap

% check input arguments

if ((nargin < 1) | (nargin > 4))
   return
elseif (nargin == 1)
   old_map = map_fig;
   MAP_SIZE = size(old_map);
   if (~isnumeric(old_map))
      return
   elseif (MAP_SIZE(1) <= 4)
      return
   elseif (MAP_SIZE(2) ~= length(color_list))
      return
   else
      control_command = 'start';
   end
else
   h_fig = map_fig;
   if (~istype(h_fig, 'figure'))
      return
   elseif (~ischar(control_command))
      return
   end
   if (nargin >= 3)
      if (~istype(h_line, 'line'))
         return
      else
         h_axes = get(h_line, 'Parent');
      end
   end
   if (nargin == 4)
      if (~isscalar(vertex_index))
         return
      end
   end
end

% initialize output to prevent MATLAB errors

switch (nargout)
case 0
case 1
   map_out = [];
case 2
   map_out = [];
   flag_out = 0;
otherwise
   return
end

% execute control command

switch (control_command)
   
case 'start'		% initiate colormap editing
   
   % open palette editor figure window
   % calculate profiles by finding slope discontinuities in colormap
   % do not allow neighboring points to be vertices
   % endpoints are included in profiles
   
   [h_fig, h_axes, h_toggle] = open_editor('editor');
   d2_map = diff(double(old_map), 2, 1);
   for i = 1 : length(color_list)
      d2_color = d2_map(:, i)';
      d2_tol = tol_scale * max(d2_color);
      d2_index = find(abs(d2_color) > d2_tol);
      d2_neighbor = diff([0 d2_index]);
      x_data = [0 d2_index(find(d2_neighbor > 1)) (MAP_SIZE(1) - 1)];
      y_data = old_map(x_data + 1, i)';
      
      % create profile lines
      % store line object handles to toggle button UserData
      % select initial profile
      
      h_line(i) = line(...
         'Parent', h_axes(1), ...
         'XData', x_data, ...
         'YData', y_data, ...
         'ButtonDownFcn', 'rgbedit(gcf, ''line'', gcbo)', ...
         'Color', color_list{i}, ...
         'Tag', color_list{i});
      set(h_toggle(i), 'UserData', h_line(i));
   end
   rgbedit(h_fig, 'select', h_line(1));
   
   % create colorbar image
   
   [new_map, color_bar] = rgbedit(h_fig, 'calculate');
   set(h_fig, 'CurrentAxes', h_axes(2));
   h_image = image([0 (size(color_bar, 2) - 1)], [1 2], color_bar);
   set(h_image, 'EraseMode', 'none');
   set(h_axes(2), 'Tag', 'colorbar');
   set(h_axes(2), 'XTickLabel', {'', '', '', '', ''});
   set(h_axes(2), 'YTick', []);
   
   % wait for figure tag value to change
   % return map is tag is 'ok'
   
   waitfor(h_fig, 'Tag');
   if (strcmp(get(h_fig, 'Tag'), 'ok'))
      [new_map, color_bar] = rgbedit(h_fig, 'calculate');
      edit_flag = 1;
   else
      new_map = old_map;
      edit_flag = 0;
   end
   
   % delete editor figure window
   % clean up global variables
   
   if (istype(h_fig, 'figure'))
      delete(h_fig);
   end
   clear global MAP_SIZE;
   
case 'line'			% determine action when user clicks on line
   
   % move point if close to point & single-click
   % delete point if close to point & double-click
   % select line if far from point & single-click
   % add point if far from point & double-click
   
   click_type = get(h_fig, 'SelectionType');
   [pt_index, pt_flag, click_pt] = isvertex(h_fig, h_axes, h_line);
   if (pt_flag)
      if (strcmp(click_type, 'normal'))
         rgbedit(h_fig, 'move', h_line, pt_index);
      elseif (strcmp(click_type, 'open'))
         rgbedit(h_fig, 'delete', h_line, pt_index);
      end
   else
      if (strcmp(click_type, 'normal'))
         rgbedit(h_fig, 'select', h_line);
      elseif (strcmp(click_type, 'open'))
         rgbedit(h_fig, 'add', h_line, click_pt(1));
      end
   end
   
case 'select'			% select a profile to edit
   
   % bring selected line to front if needed
   
   h_list = get(h_axes, 'Children');
   index = find(h_list == h_line);
   if (length(index) == 1)
      if (index ~= 1)
         h_temp = h_list(index);
         h_list(2 : index) = h_list(1 : (index - 1));
         h_list(1) = h_temp;
         set(h_axes, 'Children', h_list);
      end
   end
   
   % set line selection highlight
   
   h_list = findobj(h_axes, 'Type', 'line');
   for i = 1 : length(h_list)
      if (h_list(i) == h_line)
         set(h_list(i), 'Selected', 'on');
      else
         set(h_list(i), 'Selected', 'off');
      end
   end
   
   % set toggle button indicator
   
   h_list = findobj(h_fig, 'Style', 'togglebutton');
   for i = 1 : length(h_list)
      if (get(h_list(i), 'UserData') == h_line)
         set(h_list(i), 'Value', get(h_list(i), 'Max'));
      else
         set(h_list(i), 'Value', get(h_list(i), 'Min'));
      end
   end
   
case 'move'				% initiate the moving process
   
   % obtain profile coordinates
   % determine motion limits
   % maintain monotonic X coordinates
   % do not allow endpoints to move horizontally
   
   x_data = get(h_line, 'XData');
   y_data = get(h_line, 'YData');
   if (vertex_index == 1)
      x_lim = [x_data(1) x_data(1)];
   elseif (vertex_index == length(x_data))
      x_lim = [x_data(end) x_data(end)];
   else
      x_lim = [x_data(vertex_index - 1) x_data(vertex_index + 1)];
   end
   y_lim = get(h_axes, 'YLim');
   
   % change pointer & UserData values
   
   pointer_type = get(h_fig, 'Pointer');
   set(h_fig, 'Pointer', 'crosshair');
   fig_user = get(h_fig, 'UserData');
   set(h_fig, 'UserData', h_line);
   axes_user = get(h_axes, 'UserData');
   set(h_axes, 'UserData', [x_lim; y_lim]);
   
   % setup ButtonDown, ButtonMotion & ButtonUp callbacks
   % do not disable previous ButtonMotion callback (pixel display)
   
   button_down = get(h_fig, 'WindowButtonDownFcn');
   set(h_fig, 'WindowButtonDownFcn', '');
   button_motion = get(h_fig, 'WindowButtonMotionFcn');
   motion_fcn = sprintf('rgbedit(gcf, ''motion'', get(gcf, ''UserData''), %d)', vertex_index);
   if (isempty(button_motion))
      set(h_fig, 'WindowButtonMotionFcn', motion_fcn);
   else
      set(h_fig, 'WindowButtonMotionFcn', [button_motion '; ' motion_fcn]);
   end
   button_up = get(h_fig, 'WindowButtonUpFcn');
   set(h_fig, 'WindowButtonUpFcn', 'set(gcf, ''UserData'', ''done'')');
   h_child = [h_fig; h_axes; get(h_axes, 'Children')];
   child_down = get(h_child, 'ButtonDownFcn');
   set(h_child, 'ButtonDownFcn', '');
   
   % wait for figure UserData to be set to 'done'
   % reset figure settings
   
   waitfor(h_fig, 'UserData', 'done');
   set(h_fig, 'Pointer', pointer_type);
   set(h_fig, 'UserData', fig_user);
   set(h_fig, 'WindowButtonDownFcn', button_down);
   set(h_fig, 'WindowButtonMotionFcn', button_motion);
   set(h_fig, 'WindowButtonUpFcn', button_up);
   set(h_axes, 'UserData', axes_user);
   for i = 1 : min(length(h_child), length(child_down))
      set(h_child(i), 'ButtonDownFcn', child_down{i});
   end
   
case 'motion'			% update profile & colorbar during move
   
   % obtain motion limits
   
   limits = get(h_axes, 'UserData');
   x_lim = limits(1, :);
   y_lim = limits(2, :);
   
   % obtain current pointer position
   
   get_point = get(h_axes, 'CurrentPoint');
   x_pt = round(get_point(1, 1));
   y_pt = get_point(1, 2);
   
   % replace points only if they don't exceed limits
   
   update_flag = 0;
   if ((x_pt >= x_lim(1)) & (x_pt <= x_lim(2)))
      x_data = get(h_line, 'XData');
      x_data(vertex_index) = x_pt;
      set(h_line, 'XData', x_data);
      update_flag = 1;
   end
   if ((y_pt >= y_lim(1)) & (y_pt <= y_lim(2)))
      y_data = get(h_line, 'YData');
      y_data(vertex_index) = y_pt;
      set(h_line, 'YData', y_data);
      update_flag = 1;
   end
   
   % update color bar if needed
   
   if (update_flag == 1)
      rgbedit(h_fig, 'update');
   end
   
case 'add'				% add a vertex to the selected profile
   
   % obtain profile coordinates
   % add a point to the specified location
   % maintain monotonicity of index values
   
   x_data = get(h_line, 'XData');
   y_data = get(h_line, 'YData');
   x_pt = round(vertex_index);
   y_pt = interp1(x_data, y_data, x_pt, 'linear');
   pt_index = find((x_pt >= x_data(1 : end - 1)) & (x_pt <= x_data(2 : end)));
   if (length(pt_index) == 1)
      pt_index = pt_index + 1;
      x_data(pt_index : end + 1) = [x_pt x_data(pt_index : end)];
      y_data(pt_index : end + 1) = [y_pt y_data(pt_index : end)];
      set(h_line, 'XData', x_data);
      set(h_line, 'YData', y_data);
      rgbedit(h_fig, 'update');
   end
   
case 'delete'			% delete a vertex from a specified profile
   
   % obtain profile coordinates
   % delete specified point
   % do not delete endpoints!!
   
   x_data = get(h_line, 'XData');
   y_data = get(h_line, 'YData');
   if ((vertex_index > 1) & (vertex_index < length(x_data)))
      x_data(vertex_index : end - 1) = x_data(vertex_index + 1 : end);
      x_data(end) = [];
      y_data(vertex_index : end - 1) = y_data(vertex_index + 1 : end);
      y_data(end) = [];
      set(h_line, 'XData', x_data);
      set(h_line, 'YData', y_data);
      rgbedit(h_fig, 'update');
   end
   
case 'update'			% update colorbar display
   
   % obtain object handles for colorbar axes & image
   % create UINT8 colorbar image data
   % update colorbar image display
   
   h_axes = findobj(h_fig, 'Type', 'axes', 'Tag', 'colorbar');
   if (length(h_axes) ~= 1)
      return
   end
   h_image = findobj(h_axes, 'Type', 'image');
   if (length(h_image) ~= 1)
      return
   end
   [new_map, color_bar] = rgbedit(h_fig, 'calculate');
   set(h_image, 'CData', color_bar); 
   
case 'calculate'		% calculate the color map
   
   % obtain axes & line object handles
   
   h_axes = findobj(h_fig, 'Type', 'axes', 'Tag', 'profile');
   if (length(h_axes) ~= 1)
      return
   end
   for i = 1 : length(color_list)
      h_profile = findobj(h_axes, 'Type', 'line', 'Tag', color_list{i});
      if (length(h_profile) == 1)
         h_line(i) = h_profile;
      else
         return
      end
   end
   
   % calculate the color map from the RGB profile vertices
   
   new_map = zeros(MAP_SIZE);
   for i = 1 : min(length(h_line), size(new_map, 2))
      x = get(h_line(i), 'XData') + 1;
      y = get(h_line(i), 'YData');
      for j = 2 : min(length(x), length(y))
         new_map(x(j - 1): x(j), i) = linspace(y(j - 1), y(j), x(j) - x(j - 1) + 1)';
      end
   end
   
   % calculate the color bar for image display
   % color bar is returned as the second output (edit_flag)
   
   edit_flag = reshape(uint8(255 * new_map), 1, size(new_map, 1), size(new_map, 2));
   edit_flag = [edit_flag; edit_flag];
end

% assign output if specified

if (nargout > 0)
   map_out = new_map;
   if (nargout > 1)
      flag_out = edit_flag;
   end
end
return



function [h_fig, h_axes, h_toggle] = open_editor(fig_tag);

% OPEN_EDITOR - create palette editor figure window
%
%    [H_FIG, H_AXES, H_TOGGLE] = OPEN_EDITOR(TAG) creates a figure window
%    for interactive editing of RGB palettes.  The 'Tag' value of the new
%    figure window H_FIG is set to TAG.  H_AXES and H_TOGGLE return the
%    object handles of the new axes & toggle buttons.
%
%    The following objects are created by OPEN_EDITOR and can be accessed
%    by searching for the appropriate 'Tag' values:
%
%             1. TAG - figure for profiles, colorbar & controls
%                   CloseRequestFcn : set(gcf, 'Tag', 'cancel')
%                   Tag      : changed to 'ok' or 'cancel' when done
%             2. 'profile' - RGB profile axes
%             3. 'colorbar' - colorbar axes
%             4. 'vertex' - vertex value text label
%             5. 'red' - red profile select toggle button
%                   Callback : rgbedit(gcf, 'select', get(gcbo, 'UserData'))
%                   UserData : contains handle of corresponding profile line
%             6. 'green' - green profile select toggle button
%                   Callback : rgbedit(gcf, 'select', get(gcbo, 'UserData'))
%                   UserData : contains handle of corresponding profile line
%             7. 'blue' - blue profile select toggle button
%                   Callback : rgbedit(gcf, 'select', get(gcbo, 'UserData'))
%                   UserData : contains handle of corresponding profile line
%             8. 'cancel' - cancel button
%                   Callback : set(gcf, 'Tag', 'cancel')
%             9. 'ok' - OK button
%                   Callback : set(gcf, 'Tag', 'ok')

% By:   S.C. Molitor (smolitor@bme.jhu.edu)
% Date: April 12, 2000

% global variables

global MAP_SIZE	% size of original colormap

% check input arguments

if (nargin ~= 1)
   return
elseif (~ischar(fig_tag))
   return
end

% create figure window

h_fig = figure('Name', 'Palette Editor', ...
	'Units', 'normalized', ...
	'Position', [0.25 0.25 0.5 0.5], ...
   'Color', [0.8 0.8 0.8], ...
   'CloseRequestFcn', 'set(gcf, ''Tag'', ''cancel'')', ...
   'MenuBar', 'none', ...
   'NumberTitle', 'off', ...
   'Tag', fig_tag, ...
   'WindowButtonMotionFcn', 'rgbptr(gcbo)');

% create RGB profile axes

h_axes(1) = axes('Parent', h_fig, ...
	'Units', 'normalized', ...
	'Position', [0.1 0.35 0.8 0.575], ...
   'FontName', 'Helvetica', ...
   'FontSize', 8, ...
   'Tag', 'profile', ...
   'XLim', [0 (MAP_SIZE(1) - 1)], ...
   'YLim', [0 1]);

% create colorbar axes

h_axes(2) = axes('Parent', h_fig, ...
	'Units', 'normalized', ...
	'Position', [0.1 0.175 0.8 0.1], ...
   'XLim', [0 (MAP_SIZE(1) - 1)], ...
   'YLim', [1 2]);

% create vertex value text label

h_text = uicontrol('Parent', h_fig, ...
	'Units', 'normalized', ...
	'Position', [0.1 0.94 0.8 0.04], ...
   'BackgroundColor', [0.8 0.8 0.8], ...
	'Style', 'text', ...
   'Tag', 'vertex');

% create RGB color button labels
% create RGB profile toggle button panel

call_back = 'rgbedit(gcf, ''select'', get(gcbo, ''UserData''))';
red_label = uint8(zeros(12, 24, 3));
red_label(:, :, 1) = uint8(255);
green_label = uint8(zeros(12, 24, 3));
green_label(:, :, 2) = uint8(255);
blue_label = uint8(zeros(12, 24, 3));
blue_label(:, :, 3) = uint8(255);
h_toggle = guipanel(h_fig, [0.1 0.05 0.35 0.075], 'horizontal', ...
   'Callback', {call_back, call_back, call_back}, ...
   'CData', {red_label, green_label, blue_label}, ...
   'Min', {0, 0, 0}, ...
   'Max', {1, 1, 1}, ...
   'Style', {'togglebutton', 'togglebutton', 'togglebutton'}, ...
   'Tag', {'red', 'green', 'blue'}, ...
   'TooltipString', {'Select Red Profile', 'Select Green Profile', 'Select Blue Profile'}, ...
   'Value', {0, 0, 0});

% create Cancel/OK button panel

h_button = guipanel(h_fig, [0.55 0.05 0.35 0.075], 'horizontal', ...
   'Callback', {'set(gcf, ''Tag'', ''cancel'')', 'set(gcf, ''Tag'', ''ok'')'}, ...
   'String', {'Cancel', 'OK'}, ...
   'Style', {'pushbutton', 'pushbutton'}, ...
   'Tag', {'cancel', 'ok'});
return
